#ifndef __M_HELPER_H__
#define __M_HELPER_H__
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <sqlite3.h>
#include <random>
#include <sstream>
#include <iomanip> // setw()
#include <atomic>
#include <sys/stat.h>
#include "logger.hpp"

//提供简单的sqlite数据库操作接口，完成数据的基础增删查改操作。
class SqliteHelper
{
public:
    typedef int (*SqliteCallback)(void *, int, char **, char **);
    SqliteHelper(const std::string &dbfile) : _dbfile(dbfile), _handler(nullptr) {}
    bool open(int safe_leve = SQLITE_OPEN_FULLMUTEX)
    { // 传入安全等级
        int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_leve, nullptr);
        if (ret != SQLITE_OK)
        {
            ELOG("创建/打开sqlite数据库失败：%s", sqlite3_errmsg(_handler));
            return false;
        }
        else
            return true;
    }
    bool exec(const std::string &sql, SqliteCallback cb, void *arg)
    { // cb回调函数：执行这条语句后对结果的处理是怎样的？arg sql语句执行的结果，传到回调函数里用来拿到结果，是输出型.
        int ret = sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr);
        if (ret != SQLITE_OK)
        {
            ELOG("%s\n语句执行失败：%s", sql.c_str(), sqlite3_errmsg(_handler));
            return false;
        }
        else
            return true;
    }
    void close()
    {
        if (_handler)
            sqlite3_close_v2(_handler);
    }

private:
    std::string _dbfile;
    sqlite3 *_handler; // 句柄
};


//提供字符串分割功能
class StrHelper
{
public:
    static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &res)
    {
        size_t pos, idx = 0;
        while (idx < str.size())
        {
            pos = str.find(sep, idx);
            if (pos == std::string::npos)
            {
                // 没找到，从刚刚开始查找的位置截取到末尾
                res.push_back(str.substr(idx));
                return res.size();
            }

            if (pos == idx)
            { // 两个分隔符之间没有数据
                idx += sep.size();
                continue;
            }

            res.push_back(str.substr(idx, pos - idx));
            idx = pos + sep.size();
        }
        return res.size();
    }
};


//提供UUID-通用唯一识别码
class UUIDHelper
{
public:
    static std::string uuid()
    {
        std::random_device rd;
        std::mt19937_64 generator(rd());
        std::uniform_int_distribution<int> distribution(0, 255);

        // 8个2位十六进制    8-4-4
        std::stringstream ss;
        for (int i = 0; i < 8; i++)
        {
            ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);
            if (i == 3 || i == 5 || i == 7)
            {
                ss << '-';
            }
        }

        // 8字节序号 4-12
        static std::atomic<size_t> seq(1);
        size_t num = seq.fetch_add(1);
        for (int i = 7; i >= 0; i--)
        {
            ss << std::setw(2) << std::setfill('0') << std::hex << (num >> (i * 8) & 0xff);
            if (i == 6)
                ss << '-';
        }
        return ss.str();
    }
};


/*
    文件基础操作：
        a. 文件是否存在判断
        b. 文件大小获取
        c. 文件读/写
        d. 文件创建/删除
        e. 目录创建/删除
*/
class FileHelper{
public:
    FileHelper(const std::string &filename):_filename(filename){}
    bool exists(){
        struct stat st;
        return (stat(_filename.c_str(), &st) == 0);// access系统调用也可以，但不建议用系统调用，因为可移植性差
    }
    size_t size(){
        struct stat st;
        int ret = stat(_filename.c_str(), &st);
        if (ret < 0){
            return 0;
        }
        return st.st_size;
    }

    bool read(char *body, size_t offset, size_t len){
        //1.打开文件
        std::ifstream ifs(_filename, std::ios::binary | std::ios::in);
        if (ifs.is_open() == false){
            ELOG("%s 文件打开失败！", _filename.c_str());
            return false;
        }
        //2.跳转文件读写位置
        ifs.seekg(offset, std::ios::beg);
        //3.读取文件数据
        ifs.read(body, len);
        if (ifs.good() == false){
            ELOG("%s 文件读取数据失败！", _filename.c_str());
            ifs.close();
            return false;
        }
        //4.关闭文件
        ifs.close();
        return true;
    }

    bool read(std::string &body){
        //获取文件大小，根据文件大小调整body的空间
        size_t fsize = this->size();
        body.resize(fsize);
        return read(&body[0], 0, fsize); // 如果参数类型是string,body.c_str()得到的是const char*，所以参数类型设为char*可以接受任何类型的同时，使用&第一个元素的方法来获取
    }

    bool write(const char *body, size_t offset, size_t len){
        // 1.打开文件
        std::fstream fs(_filename, std::ios::binary | std::ios::in | std::ios::out);
        if (fs.is_open() == false){
            ELOG("%s 文件打开失败！", _filename.c_str());
            return false;
        }
        // 2.跳转文件指定位置
        fs.seekp(offset, std::ios::beg);
        // 3.写入数据
        fs.write(body, len);
        if (fs.good() == false){
            ELOG("%s 文件写入数据失败！", _filename.c_str());
            fs.close();
            return false;
        }
        // 4.关闭文件
        fs.close();
        return true;
    }

    bool write(const std::string &body){
        return write(body.c_str(), 0, body.size());
    }

    bool rename(const std::string &nname){
        return (::rename(_filename.c_str(), nname.c_str()) == 0);// rename在stdio.h，::全局
    }

    static std::string parentDirectory(const std::string &filename){
        // /aaa/bbb/ccc/ddd/test.txt 提取目录
        size_t pos = filename.find_last_of("/");
        if (pos == std::string::npos){
            //test.txt
            return "./";
        }
        std::string path = filename.substr(0, pos);
        return path;
    }

    static bool createFile(const std::string &filename){
        //要创建一个文件，打开这个不存在的文件就行了
        std::fstream ofs(filename, std::ios::binary | std::ios::out);
        if(ofs.is_open() == false){
            ELOG("%s 文件打开失败！", filename.c_str());
            return false;
        }
        ofs.close();
        return true;
    }

    static bool removeFile(const std::string &filename){
        return (::remove(filename.c_str()) == 0);
    }

    static bool createDirectory(const std::string &path){
        // aaa/bbb/ccc/ddd/
        // 在多级路径创建中，要从第一个父级目录开始创建以防不存在上一级
        // 依次创建：
        // aaa
        // aaa/bbb
        // aaa/bbb/ccc
        // aaa/bbb/ccc/ddd
        size_t pos, idx = 0;
        while (idx < path.size()){
            pos = path.find("/", idx);
            if (pos == std::string::npos){
                std::string subpath = path.substr(idx);
                return (mkdir(path.c_str(), 0775) == 0);
            }
            std::string subpath = path.substr(0, pos);
            int ret = mkdir(subpath.c_str(), 0775);
            if (ret != 0 && errno != EEXIST){
                ELOG("创建目录 %s 失败：", subpath.c_str(), strerror(errno));
                return false;
            }
            idx = pos + 1;
        }
        return true;
    }
    static bool removeDirectory(const std::string &path){
        // rm -rf path
        // 库函数stystem() <stdlib.h>
        std::string cmd = "rm -rf " + path;
        return (system(cmd.c_str()) != -1);
    }
private:
    std::string _filename;
};


#endif
